#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/types.h>
//正式学习进程等待：进程等待是必要的
//等待的作用：子进程退出如果父进程不管的话就会变为僵尸状态，变成一个无法杀死的僵尸进程，会造成资源的泄漏，所以我们可以使用进程等待的方式回收子进程的资源获取子进程的退出信息
//进程等待的方法：
//第一种方法：pid_t  wait(int *status)方法
//返回值是返回被等待的进程的pid，失败返回-1  参数是输出型参数，获取子进程的退出状态，不关心的话设置为NULL

//第二种方法：pid_t waitpid(pid_t pid,int *status,int options);
//返回值：正常返回被等待的子进程pid，如果设置了option为WNOHANG，而waitpid没有发现已退出的子进程就返回0，如果调用出错返回-1
//参数：pid是指定等待哪个进程，不关心就设置为-1 
//status：WIFEXITED（status）：若子进程正常退出返回真  ，WEXITSTATUS（status）查看子进程的退出码
//options： WNOHANG：若指定的pid进程没有结束，则waitpid返回值为0，不予等待，若正常结束则返回子进程的pid



void test1()
{
  //先模拟一下子进程退出为僵尸状态,父进程继续运行：
  pid_t id =fork();
  if(id<0)
  {
    perror("fork");
    return;
  }
  else if(id==0)
  {
    int ct=5;
    while(ct--)
    {
      printf("i am child,pid:%d, ppid:%d,ct:%d\n",getpid(),getppid(),ct);
      sleep(1);
    }
    exit(0);//直接退出进程
  }
  else{
    while(1)
    {
      printf("i am father,pid:%d,ppid:%d\n",getpid(),getppid());
      sleep(1);
    }
  }
  //会发先子进程运行结束后变为了僵尸状态，父进程并没有对其进行处理
  //注意：可以使用while :;do ps ajx |head -1 &&ps ajx |grep test.exe |grep -v grep;sleep 1;echo "------------";done持续获取对应的进程信息


}

//复制粘贴方法：
//在普通模式下，v进入可视模式，使用j，k移动光标选中文本行，y进行复制
//在普通模式下：p在光标位置进行粘贴

//接下来使用等待的方法处理子进程
void test2()
{

  pid_t id =fork();
  if(id<0)
  {
    perror("fork");
    return;
  }
  else if(id==0)
  {
    int ct=5;
    while(ct--)
    {
      printf("i am child,pid:%d, ppid:%d,ct:%d\n",getpid(),getppid(),ct);
      sleep(1);
    }
    exit(1);//直接退出进程

   //注意：这里有一个注意点：当子进程返回码不是0而是其他数字时，wait传进去的status变量打印出来却不是我们想要的正确的退出码，这是因为返回的status整形的低地址开始数的第二个字节才是记录了退出状态的退出码，所以要打印的是status>>8右移8个bit位后的数字才是正确的退出码（高位会自动补0不用担心）

  }
  else{
    int ct=10;
    while(ct--)
    {
      printf("i am father,pid:%d,ppid:%d\n",getpid(),getppid());
      sleep(1);

    }
    //父进程运行完成后，等待子进程：
    int status=0;
    //wait等待任意一个子进程退出
    pid_t ret =wait(&status);//ret是wait等待的子进程的pid
    if(ret==id)//父进程上面的id变量接收的是fork成功返回的子进程的pid
    {//等待成功的子进程的pid和我们的子进程的pid相同就说明等待成功
      printf("wait success,ret:%d,status:%d\n",ret,status>>8);//status是输出型参数，需要传入一个值的地址，就可以在函数内的值带出来
    //status就是反映子进程的状态也就是退出码，正常运行结束退出码是0,（要右移8个bit原因在上面有解释）
    
      //如果不想移动bit位也可以使用与status配套的WIFEXITED(),WEXITSTATUS()函数
      if(WIFEXITED(status))//根据status判断子进程是否正常结束，正常结束返回真
      {
        printf("进程正常结束，退出码：%d\n",WEXITSTATUS(status));//这个函数可以直接帮我们翻译status成退出码
      }
      else 
      {
        printf("进程异常");
      }
    }
    else 
    {
      printf("进程等待失败\n");
    }
    sleep(5);
    //我们可以看到父进程等待了子进程后，僵尸状态的子进程被释放了
  }
  
}


//如果创建了多个子进程呢，如何对多个子进程进行等待：
#define N 10
void RunChild()
{
  int ct=5;
  while(ct--)
  {
    printf("i am child,pid:%d, ppid:%d,ct:%d\n",getpid(),getppid(),ct);
    sleep(1);
  }
  exit(0);//子进程执行完就退出
}

void test3()
{
  for(int i=0;i<N;i++)
  {
    pid_t id=fork();
    if(id==0)
    {
      //子进程
      RunChild();

    }
    printf("create child process:%d success\n",id);
    sleep(1);
  }
  sleep(10);//创建完子进程再休眠10秒，否则子进程还没运行完成父进程就退出了

  //子进程都运行完成后，对子进程进行等待：
  for(int i=0;i<N;i++)
  {
    pid_t id=wait(NULL);//任意等待一个已经退出的子进程，id是该子进程的pid
    if(id>0)//说明等待该子进程成功
    {
      printf("wait %d success\n",id);
    }
    sleep(1);
  }
  sleep(5);
}

//进程等待小结：
//进程等待：就是系统调用wait/waitpid对子进程进行资源回收的功能
//为什么要进程等待：僵尸进程无法杀死，所以需要等待进程来杀掉它从而解决内存泄漏的问题

//注意：此时有一个问题：当子进程一直运行的时候，父进程一直在等待子进程退出从而回收资源，此时为了节省空间资源，系统会把父进程放入阻塞状态，直到子进程运行完成，再让父进程进入运行态去回收子进程
//但是我们要是想要我们的父进程继续做自己的工作，不要进入阻塞状态，等到子进程完成后又可以等待子进程呢

//就是使用非阻塞轮询
void test4()
{

  pid_t id =fork();
  if(id<0)
  {
    perror("fork");
    return;
  }
  else if(id==0)
  {
    int ct=5;
    while(ct--)
    {
      printf("i am child,pid:%d, ppid:%d,ct:%d\n",getpid(),getppid(),ct);
      sleep(1);
    }
    exit(1);
  }
  else 
  {
    int status=0;  
    //使用while(1)轮询：就要使用waitpid函数等待，并且使用WNOHANG参数表示不宕机，也就是非阻塞,组合在一起就是非阻塞轮询
    while(1)
    {
      pid_t ret =waitpid(id, &status,WNOHANG);//若此时没有接收到有子进程退出并不会在这里持续等待，而是直接返回0，也就是只等待一下，没有就不等了
      if(ret>0)//也就是此时子进程退出了，并且等待成功
       {
        if(WIFEXITED(status))//根据status判断子进程是否正常结束，正常结束返回真
        {
          printf("进程正常结束，退出码：%d\n",WEXITSTATUS(status));
        }
        else 
        {
          printf("进程异常");
        }
        break;//等待成功就可以退出轮询了
      }
      else if(ret < 0)
      {
       printf("进程等待失败\n");
       break;
      } 
      else //说明这一次等待没有等到，就可以执行父进程自己的代码了，不用阻塞的去等
      {
        printf("子进程还没结束,先运行一下自己的代码吧\n");
        sleep(1);//运行一会，否则父进程一直快速的轮询会让子进程卡死
      }
    }
    sleep(5);
  }
  
}
//这就是使用waitpid和while循环搭配组成的非阻塞轮询，可以让父进程不会一直等待子进程的结束从而进入阻塞状态，让父进程可以一直运行下去






int main()
{

  test4();
  return 0;
}
